import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import {
  CreateAxiosOptions,
  RequestOptions,
  Result,
  UploadFileParams,
} from "@/utils/http/axios/types";
import { AxiosCanceler } from "@/utils/http/axios/axiosCancel";
import { ContentTypeEnum } from "@/enums/HttpEnum";

export class VAxios {
  private axiosInstance: AxiosInstance;
  private readonly options: CreateAxiosOptions;

  /**
   * 构造函数, 使用axios.create方法, 创建axios实例.
   * @param options
   */
  constructor(options: CreateAxiosOptions) {
    this.options = options;
    this.axiosInstance = axios.create(options);
    this.setupInterceptors(); // 执行拦截器
  }

  /**
   * @description 获取拦截器具体方法
   */
  private getTransform() {
    const { transform } = this.options;
    return transform;
  }

  /**
   * @description 拦截器(请求拦截和响应拦截都在这里)
   */
  private setupInterceptors() {
    const transform = this.getTransform();

    // 如拦截器什么处理都没做.
    if (!transform) {
      return;
    }

    const {
      requestInterceptors,
      requestInterceptorsCatch,
      responseInterceptors,
      responseInterceptorsCatch,
    } = transform;

    const axiosCanceler = new AxiosCanceler();

    // 请求拦截器
    this.axiosInstance.interceptors.request.use(
      (config: AxiosRequestConfig) => {
        // // 这里的ignoreCancelToken属性是为上传文档添加的.
        // const {
        //   headers: { ignoreCancelToken },
        // } = config;
        //
        // const ignoreCancel =
        //   ignoreCancelToken !== undefined
        //     ? ignoreCancelToken
        //     : this.options.requestOptions?.ignoreCancelToken;

        const ignoreCancel = this.options.requestOptions?.ignoreCancelToken;

        !ignoreCancel && axiosCanceler.addPending(config);

        if (requestInterceptors) {
          config = requestInterceptors(config, this.options);
        }
        return config;
      },
      (error) => {
        if (requestInterceptorsCatch) {
          requestInterceptorsCatch(error);
        }
      }
    );

    // 响应拦截器
    this.axiosInstance.interceptors.response.use(
      (res: AxiosResponse) => {
        // 若res存在, 则执行.
        res && axiosCanceler.removePending(res.config);
        if (responseInterceptors) {
          res = responseInterceptors(res);
        }
        return res;
      },
      (error: Error) => {
        if (responseInterceptorsCatch) {
          responseInterceptorsCatch(error);
        }
      }
    );
  }

  /**
   * @description 请求方法
   * @param config Axios配置
   * @param options 用户自定义配置
   */
  request<T = any>(
    config: AxiosRequestConfig,
    options?: RequestOptions
  ): Promise<T> {
    // 拷贝config
    let conf: AxiosRequestConfig = Object.assign({}, config);
    // 从options中读取用户配置的请求参数
    const { requestOptions } = this.options;

    // 整合options
    const opt: RequestOptions = Object.assign({}, requestOptions, options);

    const transform = this.getTransform();

    const { beforeRequestHook, requestCatch, transformRequestData } =
      transform || {};

    if (beforeRequestHook) {
      conf = beforeRequestHook(conf, opt);
    }

    return new Promise<T>((resolve, reject) => {
      this.axiosInstance
        .request<any, AxiosResponse<Result>>(conf)
        .then((res: AxiosResponse<Result>) => {
          // 请求是否被取消掉了.
          const isCancel = axios.isCancel(res);

          // 如果请求未被取消, 且又正确的数据转换函数, 则转换数据并返回。
          if (transformRequestData && !isCancel) {
            try {
              const ret = transformRequestData(res, opt);
              resolve(ret);
            } catch (error) {
              reject(error || new Error("request error!"));
            }
            return;
          }
          resolve(res as unknown as Promise<T>);
        })
        .catch((error: Error) => {
          if (requestCatch) {
            reject(requestCatch(error));
            return;
          }
          reject(error);
        });
    });
  }

  /**
   * 文件上传功能 (图片、excel、pdf等)
   * @param config
   * @param params
   */
  uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) {
    // 上传须使用formData
    const formData = new window.FormData();

    // 上传文件的名称
    const customFilename = params.name || "file";

    if (params.filename) {
      formData.append(customFilename, params.file, params.filename);
    } else {
      formData.append(customFilename, params.file);
    }

    // 若上传文件还带有其他数据(比如创建用户并选择头像功能)
    if (params.data) {
      Object.keys(params.data).forEach((key: string) => {
        const value = params.data?.[key];
        if (Array.isArray(value)) {
          value.forEach((item) => {
            formData.append(`${key}[]`, item);
          });
          return;
        }
        formData.append(key, params.data?.[key]);
      });
    }
    return this.axiosInstance.request<T>({
      method: "POST",
      data: formData,
      headers: {
        "Content-type": ContentTypeEnum.FORM_DATA,
        ignoreCancelToken: true,
      },
      // ... 为es6 规定的扩展运算符。作用是遍历当前使用的对象能够访问到的所有属性，并将属性放入当前对象中。
      ...config,
    });
  }
}
